Skip to content

chore(ci): fix Operator tests, artifact handling, pod selectors - #4414

Merged
openshift-merge-bot[bot] merged 17 commits into
redhat-developer:mainfrom
zdrapela:fix-inherit-on-operator
Mar 23, 2026
Merged

chore(ci): fix Operator tests, artifact handling, pod selectors#4414
openshift-merge-bot[bot] merged 17 commits into
redhat-developer:mainfrom
zdrapela:fix-inherit-on-operator

Conversation

@zdrapela

@zdrapela zdrapela commented Mar 17, 2026

Copy link
Copy Markdown
Member

Description

This PR fixes several CI and E2E test issues for Operator-backed deployments:

  • Fix {{inherit}} in Operator dynamic plugins YAML: Replaces {{ "{{"}}inherit{{ "}}" }} Helm escaping with plain {{inherit}} in the dynamic plugins file dynamically generated for Operator deployments. This is a quick fix — long-term solution involves splitting value files from dynamic plugins definitions.
  • Skip Crunchy DB check on non-OCP: The Crunchy DB readiness check is only relevant for OCP clusters, so it is now skipped on K8s jobs where it was causing failures.
  • Fix artifact collection: Correct artifact subdirectory usage across tests and switch to the save_artifact helper function where possible.
  • Fix RBAC E2E tests: Update RBAC E2E tests to use the API correctly.
  • Extract RHDH deployment selector: Introduce a shared deployment-level label selector constant (BACKSTAGE_DEPLOY_SELECTOR) for both Helm and Operator deployments, replacing hardcoded pod selectors. Pod selectors are now derived from deployment matchLabels instead of being hardcoded.
  • Skip Orchestrator tests on Operator: Properly skip Orchestrator tests when running on Operator deployments.
  • Update .gitignore: Add .local-test to gitignore (it was interfering with pre-commit hooks).
  • Cleanup: Remove duplicate prepare_operator call, deduplicate pod selector logic in kube-client.ts, and remove unused variables.

It doesn't fix every one of the failing test cases, but vastly improves the current situation across both OCP and K8s platforms. Fixing everything would require spending even more time and over-inflating this PR.

Which issue(s) does this PR fix

  • Fixes #?

PR acceptance criteria

Please make sure that the following steps are complete:

  • GitHub Actions are completed and successful
  • Unit Tests are updated and passing
  • E2E Tests are updated and passing
  • Documentation is updated if necessary (requirement for new features)
  • Add a screenshot if the change is UX/UI related

How to test changes / Special notes to the reviewer

@zdrapela

Copy link
Copy Markdown
Member Author

/test e2e-ocp-operator-nightly
/test e2e-eks-operator-nightly
/test e2e-aks-operator-nightly

@github-actions

Copy link
Copy Markdown
Contributor

Image was built and published successfully. It is available at:

@zdrapela

Copy link
Copy Markdown
Member Author

/test e2e-ocp-operator-nightly
/test e2e-eks-operator-nightly
/test e2e-aks-operator-nightly

@github-actions

Copy link
Copy Markdown
Contributor

The container image build and publish workflows were skipped (either due to [skip-build] tag or no relevant changes with existing image).

@zdrapela
zdrapela force-pushed the fix-inherit-on-operator branch from f421399 to 1de4c25 Compare March 18, 2026 11:36
@github-actions

Copy link
Copy Markdown
Contributor

The container image build workflow finished with status: cancelled.

@zdrapela

Copy link
Copy Markdown
Member Author

/test e2e-ocp-operator-nightly
/test e2e-eks-operator-nightly
/test e2e-aks-operator-nightly
/test e2e-ocp-helm-nightly

@github-actions

Copy link
Copy Markdown
Contributor

Image was built and published successfully. It is available at:

@github-actions

Copy link
Copy Markdown
Contributor

The container image build and publish workflows were skipped (either due to [skip-build] tag or no relevant changes with existing image).

@zdrapela
zdrapela force-pushed the fix-inherit-on-operator branch from ebafb7a to 02958d9 Compare March 18, 2026 12:57
@zdrapela

Copy link
Copy Markdown
Member Author

/test e2e-ocp-operator-nightly
/test e2e-eks-operator-nightly
/test e2e-aks-operator-nightly
/test e2e-ocp-helm-nightly

@github-actions

Copy link
Copy Markdown
Contributor

The container image build and publish workflows were skipped (either due to [skip-build] tag or no relevant changes with existing image).

@zdrapela
zdrapela force-pushed the fix-inherit-on-operator branch from 02958d9 to 1851b53 Compare March 18, 2026 13:00
@zdrapela

Copy link
Copy Markdown
Member Author

/test e2e-ocp-operator-nightly

@github-actions

Copy link
Copy Markdown
Contributor

The container image build and publish workflows were skipped (either due to [skip-build] tag or no relevant changes with existing image).

@github-actions

Copy link
Copy Markdown
Contributor

The container image build and publish workflows were skipped (either due to [skip-build] tag or no relevant changes with existing image).

@zdrapela

Copy link
Copy Markdown
Member Author

/test e2e-ocp-operator-nightly

1 similar comment
@zdrapela

Copy link
Copy Markdown
Member Author

/test e2e-ocp-operator-nightly

@github-actions

Copy link
Copy Markdown
Contributor

The container image build and publish workflows were skipped (either due to [skip-build] tag or no relevant changes with existing image).

@zdrapela

Copy link
Copy Markdown
Member Author

/agentic_review

@rhdh-qodo-merge

rhdh-qodo-merge Bot commented Mar 20, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 📐 Spec deviations (0)

Grey Divider


Action required

1. Crunchy wait omitted 🐞 Bug ⛯ Reliability
Description
wait_for_crunchy_crd was extracted from deploy_rhdh_operator, but the OCP operator job still deploys
the base and runtime Backstage CRs without calling it. On OpenShift (where the code comment says
CrunchyDB is required), this can race the PostgresCluster CRD and cause the operator’s internal DB
provisioning to fail intermittently.
Code

.ci/pipelines/install-methods/operator.sh[R54-70]

+wait_for_crunchy_crd() {
  # Ensure PostgresCluster CRD is available before deploying Backstage CR
-  # This is required because the operator relies on CrunchyDB for its internal database
+  # This is required because the operator relies on CrunchyDB for its internal database on OpenShift
  log::info "Verifying PostgresCluster CRD is available before deploying Backstage CR..."
  k8s_wait::crd "postgresclusters.postgres-operator.crunchydata.com" 60 5 || {
    log::error "PostgresCluster CRD not available - operator won't be able to create internal database"
    return 1
  }
+}
+
+deploy_rhdh_operator() {
+  local namespace=$1
+  local backstage_crd_path=$2

-  # Verify Backstage CRD is also available
+  # Verify Backstage CRD is available
  k8s_wait::crd "backstages.rhdh.redhat.com" 60 5 || return 1
Evidence
deploy_rhdh_operator no longer checks/blocks on the PostgresCluster CRD, and wait_for_crunchy_crd is
only explicitly invoked in the RBAC deployment path in the OCP operator job; the base and runtime
operator deployments call deploy_rhdh_operator directly. The new wait_for_crunchy_crd comment also
states this CRD is required on OpenShift, so skipping it for base/runtime reintroduces the original
race condition the check was meant to prevent.

.ci/pipelines/install-methods/operator.sh[54-70]
.ci/pipelines/jobs/ocp-operator.sh[16-27]
.ci/pipelines/jobs/ocp-operator.sh[32-41]
.ci/pipelines/jobs/ocp-operator.sh[87-93]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`wait_for_crunchy_crd` was moved out of `deploy_rhdh_operator()`, but OCP operator flows still call `deploy_rhdh_operator` for **base** and **runtime** deployments without waiting for the Crunchy PostgresCluster CRD. This can cause intermittent failures on OpenShift where the operator expects CrunchyDB.

### Issue Context
- `wait_for_crunchy_crd()` exists and is documented as required on OpenShift.
- RBAC operator deployments call it, but base and runtime do not.

### Fix Focus Areas
- Add a guarded Crunchy wait (only when `IS_OPENSHIFT==true`) before applying the Backstage CR for **all** OCP operator deployments, either:
 - inside `deploy_rhdh_operator()` (conditionally), or
 - at each OCP call site (base + RBAC + runtime).

- .ci/pipelines/install-methods/operator.sh[54-97]
- .ci/pipelines/jobs/ocp-operator.sh[16-27]
- .ci/pipelines/jobs/ocp-operator.sh[87-93]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Empty selector lists pods🐞 Bug ➹ Performance
Description
waitForDeploymentReady derives podSelector only from spec.selector.matchLabels and then always calls
logPodConditions(namespace, podSelector), even when podSelector is empty. If a Deployment selector
uses matchExpressions (or otherwise has no matchLabels), this will list/log every pod in the
namespace, slowing and polluting readiness checks.
Code

e2e-tests/playwright/utils/kube-client.ts[R636-655]

+        // Derive the pod label selector from the deployment's matchLabels
+        const matchLabels = response.body.spec?.selector?.matchLabels || {};
+        const podSelector = Object.entries(matchLabels)
+          .map(([k, v]) => `${k}=${v}`)
+          .join(",");
+
        console.log(`Available replicas: ${availableReplicas}`);
        console.log(
          "Deployment conditions:",
          JSON.stringify(conditions, null, 2),
        );

        // Check for pod failure states when expecting replicas > 0
-        if (expectedReplicas > 0) {
+        if (expectedReplicas > 0 && podSelector) {
          const podFailureReason = await this.checkPodFailureStates(
            namespace,
-            labelSelector,
+            podSelector,
          );
          if (podFailureReason) {
            console.error(
Evidence
podSelector is built exclusively from matchLabels and can become an empty string; despite guarding
pod failure checks on podSelector, the code still unconditionally logs pod conditions with that
selector. logPodConditions forwards the selector directly to listNamespacedPod; an empty selector
results in an unfiltered list call (all pods).

e2e-tests/playwright/utils/kube-client.ts[617-667]
e2e-tests/playwright/utils/kube-client.ts[737-810]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`waitForDeploymentReady()` always calls `logPodConditions(namespace, podSelector)` even when `podSelector` is empty (because it is derived only from `spec.selector.matchLabels`). An empty selector causes the pod list call to return **all pods in the namespace**, creating noisy output and potential slowness/timeouts.

### Issue Context
- Some Deployments may use `spec.selector.matchExpressions` without `matchLabels`.
- There is already a safer helper `logPodConditionsForDeployment()` that avoids logging when no selector can be resolved.

### Fix Focus Areas
- In `waitForDeploymentReady()`, only call `logPodConditions` when `podSelector` is non-empty, otherwise:
 - call `logPodConditionsForDeployment(deploymentName, namespace)`, or
 - enhance selector derivation to also support `matchExpressions`.

- e2e-tests/playwright/utils/kube-client.ts[636-667]
- e2e-tests/playwright/utils/kube-client.ts[737-810]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread .ci/pipelines/install-methods/operator.sh
@github-actions

Copy link
Copy Markdown
Contributor

The container image build and publish workflows were skipped (either due to [skip-build] tag or no relevant changes with existing image).

@zdrapela

Copy link
Copy Markdown
Member Author

/test e2e-ocp-operator-auth-providers-nightly
/test e2e-eks-operator-nightly
/test e2e-aks-operator-nightly
/test e2e-gke-operator-nightly
/test e2e-ocp-helm-nightly

@zdrapela zdrapela changed the title chore(ci): fix inherit for Operator dynamic plugins yaml chore(ci,e2e): fix Operator tests, artifact handling, pod selectors Mar 20, 2026
@zdrapela zdrapela changed the title chore(ci,e2e): fix Operator tests, artifact handling, pod selectors chore(ci): fix Operator tests, artifact handling, pod selectors Mar 20, 2026
@openshift-ci

openshift-ci Bot commented Mar 20, 2026

Copy link
Copy Markdown

@zdrapela: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/e2e-ocp-operator-nightly 92d923e link false /test e2e-ocp-operator-nightly
ci/prow/e2e-aks-operator-nightly 4576353 link false /test e2e-aks-operator-nightly
ci/prow/e2e-gke-operator-nightly 4576353 link false /test e2e-gke-operator-nightly
ci/prow/e2e-eks-operator-nightly 4576353 link false /test e2e-eks-operator-nightly
ci/prow/e2e-ocp-helm-nightly 4576353 link false /test e2e-ocp-helm-nightly

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@zdrapela

Copy link
Copy Markdown
Member Author

/test e2e-eks-operator-nightly
/test e2e-aks-operator-nightly
/test e2e-gke-operator-nightly
/test e2e-ocp-helm-nightly

const conditions = response.body.status?.conditions || [];

const podSelector = await this.getDeploymentPodSelector(
deploymentName,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This getDeploymentPodSelector call is inside the while (Date.now() < endTime) polling loop, so it makes an API call to read the deployment on every iteration. The deployment's matchLabels won't change between retries — should we resolve this once before the loop and reuse it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, let me move it

@@ -658,8 +661,8 @@ export class KubeClient {
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logPodConditions now requires labelSelector as a non-optional string, but podSelector here can be null (returned by getDeploymentPodSelector). The if (expectedReplicas > 0 && podSelector) guard on line 648 only covers checkPodFailureStates, not this call. Should this also be wrapped in an if (podSelector) check?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, I'll update the pod selector so it cannot be null.

test.skip(() => skipIfJobName(JOB_NAME_PATTERNS.GKE)); // skipping orchestrator tests on GKE - plugins disabled
test.skip(() => skipIfJobName(JOB_NAME_PATTERNS.AKS)); // skipping orchestrator tests on AKS - plugins disabled
test.skip(() => skipIfJobName(JOB_NAME_PATTERNS.EKS)); // skipping orchestrator tests on EKS - plugins disabled
// TODO: https://issues.redhat.com/browse/RHDHBUGS-2184 fix orchestrator tests on Operator deployment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This block of 5 skip/fixme calls is now identical across orchestrator-entity-rbac.spec.ts, orchestrator-entity-workflows.spec.ts, and token-propagation-workflow.spec.ts. If another platform gets added you'd need to update all three. Would a shared helper like skipOrchestratorOnUnsupportedPlatforms() make sense here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is no plan to enable Orchestrator tests on K8s platforms, I'd rather use shouldSkipOrchestratorTests in the playwright.config.ts as a much cleaner option.
The test.skip is good if we want to enable them in the future.

local namespace=$1
local backstage_crd_path=$2

wait_for_crunchy_crd() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says this is only relevant "on OpenShift" but the function itself has no guard — it'll fail if ever called on a non-OCP cluster. The protection comes from call sites being in OCP-specific scripts, which is fine, but maybe add a brief note like # Caller must ensure this runs on OCP only to make the contract explicit?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually an AI comment from the codebase which wasn't correct. I'll update the description of the function to be more accurate

// This works for both Helm and Operator since both set app.kubernetes.io/name
// on the Deployment (with different values), even though pod labels differ.
const deployTarget = `$(oc get deploy -n ${namespace} -l ${deploySelector} -o name)`;
let grepCommand = `oc logs ${deployTarget} --tail=${tailNumber} -c backstage-backend -n ${namespace}`;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pre-existing, but since you're touching this line: filterWords is interpolated directly into a shell command via grep '${word}'. If any word contains a single quote, this breaks (or worse). Worth sanitizing or switching to a safer interpolation?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As you said, it's pre-existing, I barely touched the line of code, and you're suggesting a change to a code that you introduced in #2827. I don't mean to be rude, but why don't you open a PR yourself to fix it? 🤷

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zdrapela sorry I was just trying to take the opportunity to suggest a couple of improvements in your PR. It’s totally optional, feel free to ignore my comment, that’s why I didn’t mark it as “Requested changes.”

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, I get it, and thank you for the review :) It just felt a little out of place.

local url=$3
local max_attempts=${4:-30}
local wait_seconds=${5:-30}
local artifacts_subdir=$4

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a breaking positional parameter change — old $4 was max_attempts (optional), now it's artifacts_subdir (required). The in-repo caller (testing::check_and_test) is updated, but if any downstream scripts or forks call testing::check_backstage_running with the old positional args, they'd silently break (passing a number as artifacts_subdir). Just flagging the risk.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's called only once (in testing::check_and_test), I don't believe there will be any downstream scripts, or forks that will suffer from it (you either use the old version, or you rebase and use the new version).
It may make cherry-picking slightly harder, but the function has been moved to the testing:: library, so it doesn't matter.
On the other hand, it's a much more logical order.

Comment thread .ci/pipelines/utils.sh
helm::merge_values "merge" "${DIR}/value_files/${HELM_CHART_VALUE_FILE_NAME}" "${DIR}/value_files/${HELM_CHART_OSD_GCP_DIFF_VALUE_FILE_NAME}" "/tmp/merged-values_showcase_OSD-GCP.yaml"
mkdir -p "${ARTIFACT_DIR}/${NAME_SPACE}"
rsync -a "/tmp/merged-values_showcase_OSD-GCP.yaml" "${ARTIFACT_DIR}/${NAME_SPACE}/" # Save the final value-file into the artifacts directory.
common::save_artifact "${artifacts_subdir}" "/tmp/merged-values_showcase_OSD-GCP.yaml"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most other common::save_artifact calls in this PR have || true to prevent artifact-save failures from aborting the pipeline, but this one doesn't. The original rsync it replaced also lacked it, but since you're cleaning this up anyway — should it match the others for consistency?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the codebase the collection of merged yaml value files files (like this one) lack the || true, because the files should always be created and if they aren't the pipeline should fail anyway. And if the artifact collection is unsuccessful, it's making it even harder to investigate. I only see the || true is useful only if the files may not be created, which is rare in the codebase. I haven't seen a failure because of artifact collection which wasn't connected to another issue

@github-actions

Copy link
Copy Markdown
Contributor

The container image build and publish workflows were skipped (either due to [skip-build] tag or no relevant changes with existing image).

@sonarqubecloud

Copy link
Copy Markdown

@gustavolira

Copy link
Copy Markdown
Member

/lgtm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants